﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GSPatEditor.Bitmaps.DDSImpl
{
    static class DDSGeneral
    {
        #region Header Stuff
        /// <summary>
        /// Reads DDS header from file.
        /// </summary>
        /// <param name="h">Header struct.</param>
        /// <param name="r">File reader.</param>
        internal static void Read_DDS_HEADER(DDS_HEADER h, BinaryReader r)
        {
            h.dwSize = r.ReadInt32();
            h.dwFlags = r.ReadInt32();
            h.dwHeight = r.ReadInt32();
            h.dwWidth = r.ReadInt32();
            h.dwPitchOrLinearSize = r.ReadInt32();
            h.dwDepth = r.ReadInt32();
            h.dwMipMapCount = r.ReadInt32();
            for (int i = 0; i < 11; ++i)
                h.dwReserved1[i] = r.ReadInt32();
            Read_DDS_PIXELFORMAT(h.ddspf, r);
            h.dwCaps = r.ReadInt32();
            h.dwCaps2 = r.ReadInt32();
            h.dwCaps3 = r.ReadInt32();
            h.dwCaps4 = r.ReadInt32();
            h.dwReserved2 = r.ReadInt32();
        }

        /// <summary>
        /// Reads DDS pixel format.
        /// </summary>
        /// <param name="p">Pixel format struct.</param>
        /// <param name="r">File reader.</param>
        private static void Read_DDS_PIXELFORMAT(DDS_PIXELFORMAT p, BinaryReader r)
        {
            p.dwSize = r.ReadInt32();
            p.dwFlags = r.ReadInt32();
            p.dwFourCC = r.ReadInt32();
            p.dwRGBBitCount = r.ReadInt32();
            p.dwRBitMask = r.ReadUInt32();
            p.dwGBitMask = r.ReadUInt32();
            p.dwBBitMask = r.ReadUInt32();
            p.dwABitMask = r.ReadUInt32();
        }

        /// <summary>
        /// Contains information about DDS Headers. 
        /// </summary>
        public class DDS_HEADER
        {
            public int dwSize;
            public int dwFlags;
            public int dwHeight;
            public int dwWidth;
            public int dwPitchOrLinearSize;
            public int dwDepth;
            public int dwMipMapCount;
            public int[] dwReserved1 = new int[11];
            public DDS_PIXELFORMAT ddspf = new DDS_PIXELFORMAT();
            public int dwCaps;
            public int dwCaps2;
            public int dwCaps3;
            public int dwCaps4;
            public int dwReserved2;
        }

        
        /// <summary>
        /// Contains information about DDS Pixel Format.
        /// </summary>
        public class DDS_PIXELFORMAT
        {
            public int dwSize;
            public int dwFlags;
            public int dwFourCC;
            public int dwRGBBitCount;
            public uint dwRBitMask;
            public uint dwGBitMask;
            public uint dwBBitMask;
            public uint dwABitMask;

            public DDS_PIXELFORMAT()
            {
            }
        }


        /// <summary>
        /// Builds a header for DDS file format using provided information.
        /// </summary>
        /// <param name="Mips">Number of mips in image.</param>
        /// <param name="Height">Image Height.</param>
        /// <param name="Width">Image Width.</param>
        /// <param name="surfaceformat">DDS FourCC.</param>
        /// <returns>Header for DDS file.</returns>
        public static DDS_HEADER Build_DDS_Header(int Mips, int Height, int Width, ImageEngineFormat surfaceformat)
        {
            DDS_HEADER header = new DDS_HEADER();
            header.dwSize = 124;
            header.dwFlags = 0x1 | 0x2 | 0x4 | 0x0 | 0x1000 | (Mips != 1 ? 0x20000 : 0x0) | 0x0 | 0x0;  // Flags to denote fields: DDSD_CAPS = 0x1 | DDSD_HEIGHT = 0x2 | DDSD_WIDTH = 0x4 | DDSD_PITCH = 0x8 | DDSD_PIXELFORMAT = 0x1000 | DDSD_MIPMAPCOUNT = 0x20000 | DDSD_LINEARSIZE = 0x80000 | DDSD_DEPTH = 0x800000
            header.dwWidth = Width;
            header.dwHeight = Height;
            header.dwCaps = 0x1000 | (Mips != 1 ? 0 : (0x8 | 0x400000));  // Flags are: 0x8 = Optional: Used for mipmapped textures | 0x400000 = DDSCAPS_MIMPAP | 0x1000 = DDSCAPS_TEXTURE
            header.dwMipMapCount = Mips == 1 ? 1 : Mips;

            DDS_PIXELFORMAT px = new DDS_PIXELFORMAT();
            px.dwSize = 32;
            px.dwFourCC = (int)surfaceformat;
            px.dwFlags = 4;

            switch (surfaceformat)
            {
                case ImageEngineFormat.DDS_ATI2_3Dc:
                    px.dwFlags |= 0x80000;
                    header.dwPitchOrLinearSize = (int)(Width * Height);
                    break;
                case ImageEngineFormat.DDS_ATI1:
                    header.dwFlags |= 0x80000;  
                    header.dwPitchOrLinearSize = (int)(Width * Height / 2f);
                    break;
                case ImageEngineFormat.DDS_G8_L8:
                    px.dwFlags = 0x20000;
                    header.dwPitchOrLinearSize = Width * 8; // maybe?
                    header.dwFlags |= 0x8;
                    px.dwRGBBitCount = 8;
                    px.dwRBitMask = 0xFF;
                    px.dwFourCC = 0x0;
                    break;
                case ImageEngineFormat.DDS_ARGB:
                    px.dwFlags = 0x41;
                    px.dwFourCC = 0x0;
                    px.dwRGBBitCount = 32;
                    px.dwRBitMask = 0xFF0000;
                    px.dwGBitMask = 0xFF00;
                    px.dwBBitMask = 0xFF;
                    px.dwABitMask = 0xFF000000;
                    break;
                case ImageEngineFormat.DDS_V8U8:
                    px.dwFourCC = 0x0;
                    px.dwFlags = 0x80000;  // 0x80000 not actually a valid value....
                    px.dwRGBBitCount = 16;
                    px.dwRBitMask = 0xFF;
                    px.dwGBitMask = 0xFF00;
                    break;
            }
            

            header.ddspf = px;
            return header;
        }


        /// <summary>
        /// Write DDS header to stream via BinaryWriter.
        /// </summary>
        /// <param name="header">Populated DDS header by Build_DDS_Header.</param>
        /// <param name="writer">Stream to write to.</param>
        public static void Write_DDS_Header(DDS_HEADER header, BinaryWriter writer)
        {
            // KFreon: Write magic number ("DDS")
            writer.Write(0x20534444);

            // KFreon: Write all header fields regardless of filled or not
            writer.Write(header.dwSize);
            writer.Write(header.dwFlags);
            writer.Write(header.dwHeight);
            writer.Write(header.dwWidth);
            writer.Write(header.dwPitchOrLinearSize);
            writer.Write(header.dwDepth);
            writer.Write(header.dwMipMapCount);

            // KFreon: Write reserved1
            for (int i = 0; i < 11; i++)
                writer.Write(0);

            // KFreon: Write PIXELFORMAT
            DDS_PIXELFORMAT px = header.ddspf;
            writer.Write(px.dwSize);
            writer.Write(px.dwFlags);
            writer.Write(px.dwFourCC);
            writer.Write(px.dwRGBBitCount);
            writer.Write(px.dwRBitMask);
            writer.Write(px.dwGBitMask);
            writer.Write(px.dwBBitMask);
            writer.Write(px.dwABitMask);

            writer.Write(header.dwCaps);
            writer.Write(header.dwCaps2);
            writer.Write(header.dwCaps3);
            writer.Write(header.dwCaps4);
            writer.Write(header.dwReserved2);
        }
        #endregion Header Stuff

        private static Format ParseFourCC(int FourCC)
        {
            Format format = new Format();

            if (!Enum.IsDefined(typeof(ImageEngineFormat), FourCC))
                format.InternalFormat = ImageEngineFormat.DDS_ARGB;
            else
                format.InternalFormat = (ImageEngineFormat)FourCC;

            return format;
        }
        internal static Format ParseDDSFormat(Stream stream, out DDS_HEADER header)
        {
            Format format = new Format(ImageEngineFormat.DDS_ARGB);

            stream.Seek(0, SeekOrigin.Begin);
            using (BinaryReader reader = new BinaryReader(stream, Encoding.Default, true))
            {
                header = null;

                // KFreon: Check image is a DDS
                int Magic = reader.ReadInt32();
                if (Magic != 0x20534444)
                    return new Format();  // KFreon: Not a DDS

                header = new DDS_HEADER();
                Read_DDS_HEADER(header, reader);


                if (((header.ddspf.dwFlags & 0x00000004) != 0) && (header.ddspf.dwFourCC == 0x30315844 /*DX10*/))
                    throw new Exception("DX10 not supported yet!");

                format = ParseFourCC(header.ddspf.dwFourCC);

                if (format.InternalFormat == ImageEngineFormat.Unknown || format.InternalFormat == ImageEngineFormat.DDS_ARGB)
                {
                    // KFreon: Apparently all these flags mean it's a V8U8 image...
                    if (header.ddspf.dwRGBBitCount == 0x10 &&
                               header.ddspf.dwRBitMask == 0xFF &&
                               header.ddspf.dwGBitMask == 0xFF00 &&
                               header.ddspf.dwBBitMask == 0x00 &&
                               header.ddspf.dwABitMask == 0x00)
                        format = new Format(ImageEngineFormat.DDS_V8U8);  // KFreon: V8U8

                    // KFreon: Test for L8/G8
                    else if (header.ddspf.dwABitMask == 0 &&
                            header.ddspf.dwBBitMask == 0 &&
                            header.ddspf.dwGBitMask == 0 &&
                            header.ddspf.dwRBitMask == 255 &&
                            header.ddspf.dwFlags == 131072 &&
                            header.ddspf.dwSize == 32 &&
                            header.ddspf.dwRGBBitCount == 8)
                        format = new Format(ImageEngineFormat.DDS_G8_L8);

                    // KFreon: A8L8. This can probably be something else as well, but it seems to work for now
                    else if (header.ddspf.dwRGBBitCount == 16)
                        format = new Format(ImageEngineFormat.DDS_A8L8);

                    // KFreon: RGB test.
                    else if (header.ddspf.dwRGBBitCount == 24)
                        format = new Format(ImageEngineFormat.DDS_RGB);
                }

            }
            return format;
        }
        internal static List<MipMap> LoadDDS(Stream compressed, DDS_HEADER header, Format format, int desiredMaxDimension)
        {           
            List<MipMap> MipMaps = new List<MipMap>();

            int mipWidth = header.dwWidth;
            int mipHeight = header.dwHeight;

            int estimatedMips;
            if (header.dwMipMapCount != 0)
            {
                estimatedMips = header.dwMipMapCount;
            }
            else
            {
                estimatedMips = EstimateNumMipMaps(mipWidth, mipHeight);
            }

            long mipOffset = 128;  // Includes header

            // KFreon: Check number of mips is correct. i.e. For some reason, some images have more data than they should, so it can't detected it.
            // So I check the number of mips possibly contained in the image based on size and compare it to how many it should have.
            // Any image that is too short to contain all the mips it should loads only the top mip and ignores the "others".
            int testest = 0;
            var test = EnsureMipInImage(compressed.Length, mipWidth, mipHeight, 4, format, out testest, estimatedMips);  // Update number of mips too
            if (test == -1)
                estimatedMips = 1;

            // KFreon: Decide which mip to start loading at - going to just load a few mipmaps if asked instead of loading all, then choosing later. That's slow.
            if (desiredMaxDimension != 0 && estimatedMips > 1)
            {
                int tempEstimation;
                mipOffset = EnsureMipInImage(compressed.Length, mipWidth, mipHeight, desiredMaxDimension, format, out tempEstimation);  // Update number of mips too
                if (mipOffset > 128)
                {

                    double divisor = mipHeight > mipWidth ? mipHeight / desiredMaxDimension : mipWidth / desiredMaxDimension;
                    mipHeight = (int)(mipHeight / divisor);
                    mipWidth = (int)(mipWidth / divisor);

                    if (mipWidth == 0 || mipHeight == 0)  // Reset as a dimension is too small to resize
                    {
                        mipHeight = header.dwHeight;
                        mipWidth = header.dwWidth;
                        mipOffset = 128;
                    }
                    else
                        estimatedMips = tempEstimation + 1;  // cos it needs the extra one for the top?
                }
                else
                    mipOffset = 128;

                compressed.Position = mipOffset;
            }



            Func<Stream, List<byte[]>> DecompressBCBlock = null;
            Func<Stream, List<byte>> UncompressedPixelReader = null;
            switch (format.InternalFormat)
            {
                case ImageEngineFormat.DDS_RGB:
                    UncompressedPixelReader = ReadRGBPixel;
                    break;
                case ImageEngineFormat.DDS_A8L8:
                    UncompressedPixelReader = ReadA8L8Pixel;
                    break;
                case ImageEngineFormat.DDS_ARGB:  // leave this one. It has a totally different reading method and is done later
                    break;
                case ImageEngineFormat.DDS_ATI1:
                    DecompressBCBlock = DecompressATI1;
                    break;
                case ImageEngineFormat.DDS_ATI2_3Dc:
                    DecompressBCBlock = DecompressATI2Block;
                    break;
                case ImageEngineFormat.DDS_DXT1:
                    DecompressBCBlock = DecompressBC1Block;
                    break;
                case ImageEngineFormat.DDS_DXT2:
                case ImageEngineFormat.DDS_DXT3:
                    DecompressBCBlock = DecompressBC2Block;
                    break;
                case ImageEngineFormat.DDS_DXT4:
                case ImageEngineFormat.DDS_DXT5:
                    DecompressBCBlock = DecompressBC3Block;
                    break;
                case ImageEngineFormat.DDS_G8_L8:
                    UncompressedPixelReader = ReadG8_L8Pixel;
                    break;
                case ImageEngineFormat.DDS_V8U8:
                    UncompressedPixelReader = ReadV8U8Pixel;
                    break;
                default:
                    throw new Exception("ahaaha");
            }

            // KFreon: Read mipmaps
            for (int m = 0; m < estimatedMips; m++)
            {
                // KFreon: If mip is too small, skip out. This happens most often with non-square textures. I think it's because the last mipmap is square instead of the same aspect.
                if (mipWidth <= 0 || mipHeight <= 0 || compressed.Position >= compressed.Length)  // Needed cos it doesn't throw when reading past the end for some reason.
                    break;


                MipMap mipmap = null;
                if (format.IsBlockCompressed)
                    mipmap = ReadCompressedMipMap(compressed, mipWidth, mipHeight, format.BlockSize, mipOffset, DecompressBCBlock);
                else
                {
                    int mipLength = mipWidth * mipHeight * 4;

                    var array = new byte[mipLength];
                    long position = compressed.Position;

                    if (format.InternalFormat == ImageEngineFormat.DDS_ARGB)
                    {
                        compressed.Position = position;
                        compressed.Read(array, 0, array.Length);
                    }
                    else
                        mipmap = ReadUncompressedMipMap(compressed, mipWidth, mipHeight, UncompressedPixelReader);

                    if (mipmap == null)
                        mipmap = new MipMap(array, mipWidth, mipHeight);
                }

                MipMaps.Add(mipmap);

                mipOffset += mipWidth * mipHeight * format.BlockSize / 16; // Only used for BC textures

                mipWidth /= 2;
                mipHeight /= 2;

            }
            return MipMaps;
        }
        private static MipMap ReadUncompressedMipMap(Stream stream, int mipWidth, int mipHeight, Func<Stream, List<byte>> PixelReader)
        {
            // KFreon: Since mip count is an estimate, check if there are any mips left to read.
            if (stream.Position >= stream.Length)
                return null;

            int count = 0;
            byte[] mipmap = new byte[mipHeight * mipWidth * 4];
            for (int y = 0; y < mipHeight; y++)
            {
                for (int x = 0; x < mipWidth; x++)
                {
                    List<byte> bgra = PixelReader(stream);  // KFreon: Reads pixel using a method specific to the format as provided
                    mipmap[count++] = bgra[0];
                    mipmap[count++] = bgra[1];
                    mipmap[count++] = bgra[2];
                    mipmap[count++] = bgra[3];
                }
            }

            return new MipMap(mipmap, mipWidth, mipHeight);
        }
        private static MipMap ReadCompressedMipMap(Stream compressed, int mipWidth, int mipHeight, int blockSize, long mipOffset, Func<Stream, List<byte[]>> DecompressBlock)
        {
            //MemoryStream mipmap = new MemoryStream(4 * mipWidth * mipHeight);
            byte[] mipmapData = new byte[4 * mipWidth * mipHeight];

            // Loop over rows and columns NOT pixels
            int compressedLineSize = blockSize * mipWidth / 4;
            int bitsPerScanline = 4 * (int)mipWidth;
            ParallelOptions po = new ParallelOptions();
            po.MaxDegreeOfParallelism = -1;
            int texelCount = mipHeight / 4;
            if (texelCount != 0)
            {
                Parallel.For(0, texelCount, po, (rowr, loopstate) =>
                //for (int rowr = 0; rowr < texelCount; rowr++)
                {
                    int row = rowr;
                    using (MemoryStream DecompressedLine = ReadBCMipLine(compressed, mipHeight, mipWidth, bitsPerScanline, mipOffset, compressedLineSize, row, DecompressBlock))
                    {
                        if (DecompressedLine != null)
                            lock (mipmapData)
                            {
                                int index = row * bitsPerScanline * 4;
                                DecompressedLine.Position = 0;
                                DecompressedLine.Read(mipmapData, index, DecompressedLine.Length > mipmapData.Length ? mipmapData.Length : (int)DecompressedLine.Length);
                            }
                        else
                            loopstate.Break();
                    }
                });
            }

            return new MipMap(mipmapData, mipWidth, mipHeight);
        }
        private static int ReadFrom(this Stream TargetStream, Stream SourceStream, int Length, int bufferSize = 4096)
        {
            byte[] buffer = new byte[bufferSize];
            int num = 0;
            do
            {
                int num2 = SourceStream.Read(buffer, 0, Math.Min(bufferSize, Length));
                bool flag = num2 == 0;
                if (flag)
                {
                    break;
                }
                Length -= num2;
                TargetStream.Write(buffer, 0, num2);
                num += num2;
            }
            while (Length > 0);
            return num;
        }
        private static MemoryStream ReadBCMipLine(Stream compressed, int mipHeight, int mipWidth, int bitsPerScanLine, long mipOffset, int compressedLineSize, int rowIndex, Func<Stream, List<byte[]>> DecompressBlock)
        {
            int bitsPerPixel = 4;

            MemoryStream DecompressedLine = new MemoryStream(bitsPerScanLine * 4);

            // KFreon: Read compressed line into new stream for multithreading purposes
            using (MemoryStream CompressedLine = new MemoryStream(compressedLineSize))
            {
                lock (compressed)
                {
                    // KFreon: Seek to correct texel
                    compressed.Position = mipOffset + rowIndex * compressedLineSize;  // +128 = header size

                    // KFreon: since mip count is an estimate, check to see if there are any mips left to read.
                    if (compressed.Position >= compressed.Length)
                        return null;

                    // KFreon: Read compressed line
                    CompressedLine.ReadFrom(compressed, compressedLineSize);
                    if (CompressedLine.Length < compressedLineSize)
                    {
                        //debug break
                    }
                }
                CompressedLine.Position = 0;

                // KFreon: Read texels in row
                for (int column = 0; column < mipWidth; column += 4)
                {
                    try
                    {
                        // decompress 
                        List<byte[]> decompressed = DecompressBlock(CompressedLine);
                        byte[] blue = decompressed[0];
                        byte[] green = decompressed[1];
                        byte[] red = decompressed[2];
                        byte[] alpha = decompressed[3];


                        // Write texel
                        int TopLeft = column * bitsPerPixel;// + rowIndex * 4 * bitsPerScanLine;  // Top left corner of texel IN BYTES (i.e. expanded pixels to 4 channels)
                        DecompressedLine.Seek(TopLeft, SeekOrigin.Begin);
                        byte[] block = new byte[16];
                        for (int i = 0; i < 16; i += 4)
                        {
                            // BGRA
                            for (int j = 0; j < 16; j += 4)
                            {
                                block[j] = blue[i + (j >> 2)];
                                block[j + 1] = green[i + (j >> 2)];
                                block[j + 2] = red[i + (j >> 2)];
                                block[j + 3] = alpha[i + (j >> 2)];
                            }
                            DecompressedLine.Write(block, 0, 16);

                            // Go one line of pixels down (bitsPerScanLine), then to the left side of the texel (4 pixels back from where it finished)
                            DecompressedLine.Seek(bitsPerScanLine - bitsPerPixel * 4, SeekOrigin.Current);
                        }
                    }
                    catch
                    {
                        // Ignore. Most likely error reading smaller mips that don't behave
                    }

                }
            }

            return DecompressedLine;
        }
        private static long EnsureMipInImage(long streamLength, int mainWidth, int mainHeight, int desiredMaxDimension, Format format, out int numMipMaps, double mipIndex = -1)
        {
            // TODO: Is the other estimated mips required?

            if (mainWidth <= desiredMaxDimension && mainHeight <= desiredMaxDimension)
            {
                numMipMaps = EstimateNumMipMaps(mainWidth, mainHeight);
                return 128; // One mip only
            }


            int dependentDimension = mainWidth > mainHeight ? mainWidth : mainHeight;

            mipIndex = mipIndex == -1 ? Math.Log((dependentDimension / desiredMaxDimension), 2) - 1 : mipIndex;
            if (mipIndex < -1)
                throw new InvalidDataException(@"Invalid dimensions for mipmapping. Got desired: {desiredMaxDimension} and dependent: {dependentDimension}");


            int requiredOffset = (int)ExpectedImageSize(mipIndex, format, mainHeight, mainWidth);  // +128 for header

            int limitingDimension = mainWidth > mainHeight ? mainHeight : mainWidth;
            double newDimDivisor = limitingDimension * 1f / desiredMaxDimension;
            numMipMaps = EstimateNumMipMaps((int)(mainWidth / newDimDivisor), (int)(mainHeight / newDimDivisor));

            // Should only occur when an image has no mips
            if (streamLength < requiredOffset)
                return -1;

            return requiredOffset;
        }
        private static List<byte> ReadG8_L8Pixel(Stream fileData)
        {
            byte red = (byte)fileData.ReadByte();
            byte green = red;
            byte blue = red;  // KFreon: Same colour for other channels to make grayscale.

            return new List<byte>() { blue, green, red, 0xFF };
        }
        private static byte[] ReadBytesFromStream(this Stream stream, int length)
        {
            var ret = new byte[length];
            stream.Read(ret, 0, length);
            return ret;
        }
        private static List<byte> ReadV8U8Pixel(Stream fileData)
        {
            byte[] rg = fileData.ReadBytesFromStream(2);
            byte red = (byte)(rg[0] - 130);  // KFreon: Don't really know why this 130 is here, but it gives the correct pixel values.
            byte green = (byte)(rg[1] - 130);
            byte blue = 0xFF;

            return new List<byte>() { blue, green, red, 0xFF };
        }
        private static List<byte> ReadRGBPixel(Stream fileData)
        {
            var rgb = fileData.ReadBytesFromStream(3);
            byte red = rgb[0];
            byte green = rgb[1];
            byte blue = rgb[2];
            return new List<byte>() { red, green, blue, 0xFF };
        }
        private static List<byte> ReadA8L8Pixel(Stream fileData)
        {
            var al = fileData.ReadBytesFromStream(2);
            return new List<byte>() { al[0], al[0], al[0], al[1] };
        }
        private static List<byte[]> DecompressATI1(Stream compressed)
        {
            byte[] channel = DDSGeneral.Decompress8BitBlock(compressed, false);
            List<byte[]> DecompressedBlock = new List<byte[]>();

            // KFreon: All channels are the same to make grayscale.
            DecompressedBlock.Add(channel);
            DecompressedBlock.Add(channel);
            DecompressedBlock.Add(channel);

            // KFreon: Alpha needs to be 255
            byte[] alpha = new byte[16];
            for (int i = 0; i < 16; i++)
                alpha[i] = 0xFF;
            DecompressedBlock.Add(alpha);
            return DecompressedBlock;
        }
        private static List<byte[]> DecompressATI2Block(Stream compressed)
        {
            byte[] red = DDSGeneral.Decompress8BitBlock(compressed, false);
            byte[] green = DDSGeneral.Decompress8BitBlock(compressed, false);
            List<byte[]> DecompressedBlock = new List<byte[]>();

            // KFreon: Alpha needs to be 255
            byte[] alpha = new byte[16];
            byte[] blue = new byte[16];
            for (int i = 0; i < 16; i++)
            {
                alpha[i] = 0xFF;
                /*double r = red[i] / 255.0;
                double g = green[i] / 255.0;
                double test = 1 - (r * g);
                double anbs = Math.Sqrt(test);
                double ans = anbs * 255.0;*/
                blue[i] = (byte)0xFF;
            }

            DecompressedBlock.Add(blue);
            DecompressedBlock.Add(green);
            DecompressedBlock.Add(red);
            DecompressedBlock.Add(alpha);

            return DecompressedBlock;
        }
        private static byte[] Decompress8BitBlock(Stream compressed, bool isSigned)
        {
            byte[] DecompressedBlock = new byte[16];

            // KFreon: Read min and max colours (not necessarily in that order)
            byte[] block = new byte[8];
            compressed.Read(block, 0, 8);

            byte min = block[0];
            byte max = block[1];

            byte[] Colours = Build8BitPalette(min, max, isSigned);

            // KFreon: Decompress pixels
            ulong bitmask = (ulong)block[2] << 0 | (ulong)block[3] << 8 | (ulong)block[4] << 16 |   // KFreon: Read all 6 compressed bytes into single 
                (ulong)block[5] << 24 | (ulong)block[6] << 32 | (ulong)block[7] << 40;


            // KFreon: Bitshift and mask compressed data to get 3 bit indicies, and retrieve indexed colour of pixel.
            for (int i = 0; i < 16; i++)
                DecompressedBlock[i] = (byte)Colours[bitmask >> (i * 3) & 0x7];

            return DecompressedBlock;
        }
        private static byte[] Build8BitPalette(byte min, byte max, bool isSigned)
        {
            byte[] Colours = new byte[8];
            Colours[0] = min;
            Colours[1] = max;

            // KFreon: Choose which type of interpolation is required
            if (min > max)
            {
                // KFreon: Interpolate other colours
                for (int i = 2; i < 8; i++)
                {
                    double test = min + (max - min) * (i - 1) / 7;
                    Colours[i] = (byte)test;
                }
            }
            else
            {
                // KFreon: Interpolate other colours and add Opacity or something...
                for (int i = 2; i < 6; i++)
                {
                    //double test = ((8 - i) * min + (i - 1) * max) / 5.0f;   // KFreon: "Linear interpolation". Serves me right for trusting a website without checking it...
                    double extratest = min + (max - min) * (i - 1) / 5;
                    Colours[i] = (byte)extratest;
                }
                Colours[6] = (byte)(isSigned ? -254 : 0);  // KFreon: snorm and unorm have different alpha ranges
                Colours[7] = 255;
            }

            return Colours;
        }
        private static List<byte[]> DecompressBC1Block(Stream compressed)
        {
            return DDSGeneral.DecompressRGBBlock(compressed, true);
        }
        private static List<byte[]> DecompressBC2Block(Stream compressed)
        {
            // KFreon: Read alpha into byte[] for maximum speed? Might be cos it's a MemoryStream...
            byte[] CompressedAlphas = new byte[8];
            compressed.Read(CompressedAlphas, 0, 8);
            int count = 0;

            // KFreon: Read alpha
            byte[] alpha = new byte[16];
            for (int i = 0; i < 16; i += 2)
            {
                //byte twoAlphas = (byte)compressed.ReadByte();
                byte twoAlphas = CompressedAlphas[count++];
                for (int j = 0; j < 2; j++)
                    alpha[i + j] = (byte)(twoAlphas << (j * 4));
            }


            // KFreon: Organise output by adding alpha channel (channel read in RGB block is empty)
            List<byte[]> DecompressedBlock = DDSGeneral.DecompressRGBBlock(compressed, false);
            DecompressedBlock[3] = alpha;
            return DecompressedBlock;
        }
        private static List<byte[]> DecompressBC3Block(Stream compressed)
        {
            byte[] alpha = DDSGeneral.Decompress8BitBlock(compressed, false);
            List<byte[]> DecompressedBlock = DDSGeneral.DecompressRGBBlock(compressed, false);
            DecompressedBlock[3] = alpha;
            return DecompressedBlock;
        }
        private static List<byte[]> DecompressRGBBlock(Stream compressed, bool isDXT1)
        {
            int[] DecompressedBlock = new int[16];

            ushort colour0;
            ushort colour1;
            byte[] pixels = null;
            int[] Colours = null;

            List<byte[]> DecompressedChannels = new List<byte[]>(4);
            byte[] red = new byte[16];
            byte[] green = new byte[16];
            byte[] blue = new byte[16];
            byte[] alpha = new byte[16];
            DecompressedChannels.Add(blue);
            DecompressedChannels.Add(green);
            DecompressedChannels.Add(red);
            DecompressedChannels.Add(alpha);

            try
            {
                using (BinaryReader reader = new BinaryReader(compressed, Encoding.Default, true))
                {
                    // Read min max colours
                    colour0 = (ushort)reader.ReadInt16();
                    colour1 = (ushort)reader.ReadInt16();
                    Colours = BuildRGBPalette(colour0, colour1, isDXT1);

                    // Decompress pixels
                    pixels = reader.ReadBytes(4);
                }
            }
            catch (EndOfStreamException)
            {
                Console.WriteLine();
                // It's due to weird shaped mips at really low resolution. Like 2x4

                return DecompressedChannels;
            }


            for (int i = 0; i < 16; i += 4)
            {
                //byte bitmask = (byte)compressed.ReadByte();
                byte bitmask = pixels[i / 4];
                for (int j = 0; j < 4; j++)
                    DecompressedBlock[i + j] = Colours[bitmask >> (2 * j) & 0x03];
            }

            // KFreon: Decode into BGRA
            for (int i = 0; i < 16; i++)
            {
                int colour = DecompressedBlock[i];
                var rgb = ReadDXTColour(colour);
                red[i] = rgb[0];
                green[i] = rgb[1];
                blue[i] = rgb[2];
                alpha[i] = 0xFF;
            }
            return DecompressedChannels;
        }
        private static int EstimateNumMipMaps(int Width, int Height)
        {
            int limitingDimension = Width > Height ? Height : Width;
            return (int)Math.Log(limitingDimension, 2); // There's 10 mipmaps besides the main top one.
        }
        private static double ExpectedImageSize(double mipIndex, Format format, int baseWidth, int baseHeight)
        {
            int num = 1;
            bool isBlockCompressed = format.IsBlockCompressed;
            if (isBlockCompressed)
            {
                num = 4;
            }
            double num2 = (mipIndex == -1.0) ? 0.0 : (0.33333333333333331 * (4.0 - Math.Pow(4.0, -mipIndex)));
            return 128.0 + num2 * (double)format.BlockSize * (double)(baseWidth / num) * (double)(baseHeight / num);
        }
        private static int[] BuildRGBPalette(int Colour0, int Colour1, bool isDXT1)
        {
            int[] Colours = new int[4];

            Colours[0] = Colour0;
            Colours[1] = Colour1;

            var Colour0s = ReadDXTColour(Colour0);
            var Colour1s = ReadDXTColour(Colour1);


            // Interpolate other 2 colours
            if (Colour0 > Colour1)
            {
                var r1 = (byte)(2f / 3f * Colour0s[0] + 1f / 3f * Colour1s[0]);
                var g1 = (byte)(2f / 3f * Colour0s[1] + 1f / 3f * Colour1s[1]);
                var b1 = (byte)(2f / 3f * Colour0s[2] + 1f / 3f * Colour1s[2]);

                var r2 = (byte)(1f / 3f * Colour0s[0] + 2f / 3f * Colour1s[0]);
                var g2 = (byte)(1f / 3f * Colour0s[1] + 2f / 3f * Colour1s[1]);
                var b2 = (byte)(1f / 3f * Colour0s[2] + 2f / 3f * Colour1s[2]);

                Colours[2] = BuildDXTColour(r1, g1, b1);
                Colours[3] = BuildDXTColour(r2, g2, b2);
            }
            else
            {
                // KFreon: Only for dxt1
                var r = (byte)(1 / 2f * Colour0s[0] + 1 / 2f * Colour1s[0]);
                var g = (byte)(1 / 2f * Colour0s[1] + 1 / 2f * Colour1s[1]);
                var b = (byte)(1 / 2f * Colour0s[2] + 1 / 2f * Colour1s[2]);

                Colours[2] = BuildDXTColour(r, g, b);
                Colours[3] = 0;
            }
            return Colours;
        }
        private static byte[] ReadDXTColour(int colour)
        {
            // Read RGB 5:6:5 data
            var b = (colour & 0x1F);
            var g = (colour & 0x7E0) >> 5;
            var r = (colour & 0xF800) >> 11;

            // Expand to 8 bit data
            byte testr = (byte)(r << 3);
            byte testg = (byte)(g << 2);
            byte testb = (byte)(b << 3);
            return new byte[3] { testr, testg, testb };
        }
        private static int BuildDXTColour(byte r, byte g, byte b)
        {
            // Compress to 5:6:5
            byte r1 = (byte)(r >> 3);
            byte g1 = (byte)(g >> 2);
            byte b1 = (byte)(b >> 3);

            return (r1 << 11) | (g1 << 5) | (b1);
        }
    }
}
